Le carnet On est
Annale · session normale 2020-2021 · ING2 MF

Examen C++ 2020 — 2021 · MF

Un gros sujet de programmation (12 pts) : simuler un ordonnanceur de processus avec priority_queue STL, foncteur de comparaison, héritage depuis une classe abstraite Objet, saisie sécurisée par exceptions.

2h · ING2 MF 17/11/2020 Pas d'accès à Internet

i. Le sujet

Le PDF ne s'affiche pas. Télécharger.

↗ Ouvrir en plein écran

ii. Notions mobilisées

BlocNotion cléChapitre
Classe abstraite Objet + afficher() virtuelle purePolymorphisme dynamique, classes abstraitesSE4 §7
Héritage Processus / OrdonnanceurHiérarchie, mot-clé public, redéfinitionSE4
priority_queue<Processus> + foncteurSTL, container associatif, operator()SE7
Saisie sécurisée par exceptionstry/catch/throwSE6
Affichage avec sleep() de <unistd.h>I/O console, temporisation systèmeSE5

iii. Repères de correction

Architecture demandée — 4 classes

Hiérarchie
  • Objet : classe abstraite avec virtual void afficher() = 0;
  • Processus : hérite d'Objet, redéfinit afficher(). Attributs id, duree, priority
  • Ordonnanceur : hérite d'Objet, contient un priority_queue<Processus, vector<Processus>, Comparaison>
  • Comparaison : foncteur fourni avec bool operator()(const Processus&, const Processus&)

Pourquoi Objet abstrait avec afficher() virtuelle pure ?

L'énoncé demande la justification. Réponse type :

  • L'interface commune afficher() permet de manipuler de manière uniforme les objets héritiers (Processus et Ordonnanceur) à travers un pointeur sur Objet.
  • Le rendre virtuelle pure impose à chaque classe dérivée de définir son propre afficher() — c'est un contrat de polymorphisme dynamique. La classe Objet ne peut pas être instanciée seule, ce qui empêche les erreurs d'usage.

Classe Processus

// processus.h
#include "objet.h"

class Processus : public Objet {
public:
   int id;
   int duree;
   int priority;

   Processus(int _id, int _duree, int _prio)
      : id(_id), duree(_duree), priority(_prio) { }

   virtual void afficher() override {
      cout << "P(" << id << ", d=" << duree
           << ", prio=" << priority << ")";
   }
};

Classe Ordonnanceur

// ordonnanceur.h
#include <queue>
#include <vector>
#include "processus.h"

class Comparaison {
public:
   bool operator()(const Processus& p1, const Processus& p2) {
      return p1.priority < p2.priority;     // plus grand prio = plus prioritaire
   }
};

class Ordonnanceur : public Objet {
public:
   priority_queue<Processus, vector<Processus>, Comparaison> pq;
   int quantum = 100;                    // ms

   void ajouter(const Processus& p) { pq.push(p); }

   void traiter() {
      while (!pq.empty()) {
         Processus p = pq.top(); pq.pop();
         cout << "Traitement de "; p.afficher();
         cout << endl;

         p.duree -= quantum;
         if (p.priority > 1) p.priority--;
         sleep(1);                          // temporisation visuelle

         if (p.duree > 0) pq.push(p);     // remettre dans la file
         else cout << "P(" << p.id << ") terminé\n";
      }
   }

   virtual void afficher() override {
      cout << "Ordonnanceur (" << pq.size() << " processus)";
   }
};

Saisie sécurisée avec exceptions

int saisirInt(const string& msg, int min, int max) {
   int a;
   while (true) {
      try {
         cout << msg << " [" << min << ", " << max << "] : ";
         cin >> a;
         if (cin.fail()) { cin.clear(); cin.ignore(1000, '\n'); throw 10; }
         if (a < min || a > max) throw 20;
         return a;
      } catch (int) {
         cout << "Erreur de saisie, recommence.\n";
      }
   }
}

Programme principal

#include <iostream>
#include <unistd.h>                          // pour sleep()
#include "ordonnanceur.h"
using namespace std;

int main(int argc, char** argv) {
   if (argc != 2) {
      cerr << "Usage : " << argv[0] << " nbProcessus" << endl;
      return 1;
   }

   int nb = atoi(argv[1]);
   Ordonnanceur ord;

   for (int i = 0; i < nb; i++) {
      cout << "\n--- Processus #" << (i+1) << " ---\n";
      int duree = saisirInt("durée (ms)", 100, 10000);
      int prio  = saisirInt("priorité", 1, 10);
      ord.ajouter(Processus(i+1, duree, prio));
   }

   ord.traiter();
   return 0;
}

Compilation : g++ *.cpp -Wall -o ordonnanceur · Exécution : ./ordonnanceur 4.

Détails à ne pas oublier
  • L'énoncé précise que la priorité décroît de 1 unité à chaque traitement, jusqu'à 1 (puis reste constante). Cela simule un mécanisme de nice Unix.
  • Quand duree ≤ 0, le processus est supprimé de la file (pas remis).
  • priority_queue avec un foncteur operator() qui compare p1.priority < p2.priority mettra le plus grand en tête (max-heap).
  • sleep(1) (Linux) ou Sleep(1000) (Windows) entre chaque traitement pour bien suivre la simulation.

Notions ré-activées

  • Classe abstraite : virtual T m() = 0;
  • Héritage public : la classe fille hérite de l'interface, redéfinit les virtuelles
  • priority_queue<T, vector<T>, Cmp> : max-heap par défaut
  • Foncteur de comparaison : classe avec bool operator()(const T&, const T&)
  • Méthodes principales : push, top, pop, empty, size
  • Saisie sécurisée : boucle try/catch avec throw sur erreur
  • cin.fail() + cin.clear() + cin.ignore() pour reset le flux après erreur
  • sleep(s) Linux / Sleep(ms) Windows depuis <unistd.h> ou <windows.h>
  • Compilation multi-fichiers : g++ *.cpp -Wall -o exec